package com.ruoyi.iot.service.health.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.iot.domain.entity.DeviceAlarmWindow;
import com.ruoyi.iot.domain.entity.DeviceElectricalWindow;
import com.ruoyi.iot.domain.entity.DeviceFaultWindow;
import com.ruoyi.iot.domain.entity.DeviceHealthHistory;
import com.ruoyi.iot.domain.entity.DeviceHealthScore;
import com.ruoyi.iot.domain.entity.DeviceTemperatureWindow;
import com.ruoyi.iot.domain.entity.DeviceThresholdConfig;
import com.ruoyi.iot.mapper.DeviceAlarmWindowMapper;
import com.ruoyi.iot.mapper.DeviceElectricalWindowMapper;
import com.ruoyi.iot.mapper.DeviceFaultWindowMapper;
import com.ruoyi.iot.mapper.DeviceHealthHistoryMapper;
import com.ruoyi.iot.mapper.DeviceHealthScoreMapper;
import com.ruoyi.iot.mapper.DeviceTemperatureWindowMapper;
import com.ruoyi.iot.mapper.DeviceThresholdConfigMapper;
import com.ruoyi.iot.service.HostDeviceMetricsService;
import com.ruoyi.iot.service.health.DeviceHealthScoreService;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 设备健康评分服务实现类。
 */
@Service
public class DeviceHealthScoreServiceImpl implements DeviceHealthScoreService {

    private static final Logger log = LoggerFactory.getLogger(DeviceHealthScoreServiceImpl.class);

    private static final int TEMPERATURE_WINDOW = 10;
    private static final int ELECTRICAL_WINDOW = 10;
    private static final int ALARM_WINDOW = 5;
    private static final int FAULT_WINDOW = 30;

    private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
    private static final BigDecimal ZERO = BigDecimal.ZERO;
    private static final BigDecimal DEFAULT_WEIGHT = BigDecimal.valueOf(0.25);

    private final HostDeviceMetricsService hostDeviceMetricsService;
    private final DeviceTemperatureWindowMapper temperatureWindowMapper;
    private final DeviceElectricalWindowMapper electricalWindowMapper;
    private final DeviceAlarmWindowMapper alarmWindowMapper;
    private final DeviceFaultWindowMapper faultWindowMapper;
    private final DeviceThresholdConfigMapper thresholdConfigMapper;
    private final DeviceHealthScoreMapper healthScoreMapper;
    private final DeviceHealthHistoryMapper healthHistoryMapper;
    private final ObjectMapper objectMapper;

    public DeviceHealthScoreServiceImpl(HostDeviceMetricsService hostDeviceMetricsService,
                                        DeviceTemperatureWindowMapper temperatureWindowMapper,
                                        DeviceElectricalWindowMapper electricalWindowMapper,
                                        DeviceAlarmWindowMapper alarmWindowMapper,
                                        DeviceFaultWindowMapper faultWindowMapper,
                                        DeviceThresholdConfigMapper thresholdConfigMapper,
                                        DeviceHealthScoreMapper healthScoreMapper,
                                        DeviceHealthHistoryMapper healthHistoryMapper,
                                        ObjectMapper objectMapper) {
        this.hostDeviceMetricsService = hostDeviceMetricsService;
        this.temperatureWindowMapper = temperatureWindowMapper;
        this.electricalWindowMapper = electricalWindowMapper;
        this.alarmWindowMapper = alarmWindowMapper;
        this.faultWindowMapper = faultWindowMapper;
        this.thresholdConfigMapper = thresholdConfigMapper;
        this.healthScoreMapper = healthScoreMapper;
        this.healthHistoryMapper = healthHistoryMapper;
        this.objectMapper = objectMapper;
    }

    /**
     * 计算最新的健康评分并写入数据库。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void calculateLatestScores() {
        // 查询所有主柜的最新指标窗口数据，作为实时健康评分的输入
        // 复用主柜指标服务获取需要汇总的设备列表
        List<Map<String, Object>> devices = hostDeviceMetricsService.queryAllMainCabinetMetrics();
        // 没有可计算的设备时仅记录日志，避免重复触发计算
        // 无历史数据可汇总时直接返回，避免空跑
        if (CollectionUtils.isEmpty(devices)) {
            log.info("未查询到主柜设备监测数据，跳过健康评分计算");
        }
        // 记录统一的计算时间戳，保证同批结果时间一致
        LocalDateTime now = LocalDateTime.now();
        // 收集需要落库的设备得分
        List<DeviceHealthScore> scores = new ArrayList<>();
        // 遍历每台主机执行评分流程
        for (Map<String, Object> device : devices) {
            String hostId = MapUtils.getString(device, "device_id"); // 主机ID用于定位温度、报警、故障窗口
            String cabinetId = MapUtils.getString(device, "device_kg_id"); // 开关柜ID用于定位电参窗口
            if (StringUtils.isBlank(hostId)) {
                // 遇到未绑定主机标识的数据时直接跳过，防止空指针
                continue;
            }
            Map<String, ThresholdRule> rules = loadThresholdRules(hostId); // 加载设备专属阈值规则，优先设备级配置
            DeviceTemperatureWindow temperatureWindow = temperatureWindowMapper.selectLatestWindow(hostId, TEMPERATURE_WINDOW); // 温度窗口：默认10分钟内的最高温值

            DeviceAlarmWindow alarmWindow = alarmWindowMapper.selectLatestWindow(RuoYiConfig.getArea().equals("jht")? cabinetId : hostId, ALARM_WINDOW); // 报警窗口：默认5分钟报警次数
            DeviceFaultWindow faultWindow = faultWindowMapper.selectLatestWindow(RuoYiConfig.getArea().equals("jht")? cabinetId : hostId, FAULT_WINDOW); // 故障窗口：默认30天故障统计


            DeviceElectricalWindow electricalWindow = null; // 电参窗口：先查主柜关联的开关柜，再回退主机自身
            if (StringUtils.isNotBlank(cabinetId)) {
                electricalWindow = electricalWindowMapper.selectLatestWindow(cabinetId, ELECTRICAL_WINDOW);
            }
            if (electricalWindow == null) {
                electricalWindow = electricalWindowMapper.selectLatestWindow(hostId, ELECTRICAL_WINDOW);
            }
            if (temperatureWindow == null && alarmWindow == null && faultWindow == null && electricalWindow == null) {
                // 四类数据窗口均缺失时无法形成评分，跳过该设备
                continue;
            }
            Map<String, MetricValue> metricValues = collectMetricValues(temperatureWindow, alarmWindow, faultWindow, electricalWindow); // 汇总各指标原始值
            DeviceHealthScore score = buildScore(hostId, metricValues, rules, temperatureWindow, alarmWindow, faultWindow, electricalWindow, now); // 根据规则计算各维度得分
            if (score != null) {
                scores.add(score); // 收集成功生成的得分记录，稍后批量写入
            }
        }
        if (CollectionUtils.isNotEmpty(scores)) {
            healthScoreMapper.upsertBatch(scores); // 批量更新最新得分快照，减少数据库写入次数
        }
    }
    /**
     * 聚合日、月、年维度的健康历史数据。
     */
    @Transactional(rollbackFor = Exception.class)
    public void aggregateDailyHistory() {
        // 复用主柜指标服务获取需要汇总的设备列表
        List<Map<String, Object>> devices = hostDeviceMetricsService.queryAllMainCabinetMetrics();
        // 无历史数据可汇总时直接返回，避免空跑
        if (CollectionUtils.isEmpty(devices)) {
            log.info("未查询到主柜设备监测数据，跳过健康评分计算");
            return;
        }
        LocalDate today = LocalDate.now(); // 当天日期作为日度统计基准
        LocalDateTime dayStart = today.atStartOfDay(); // 日维度开始时间（含）
        LocalDateTime dayEnd = dayStart.plusDays(1); // 日维度结束时间（不含）
        LocalDate monthStartDate = today.withDayOfMonth(1); // 月初日期作为月度统计基准
        LocalDate yearStartDate = today.withDayOfYear(1); // 年初日期作为年度统计基准
        LocalDateTime monthStart = monthStartDate.atStartOfDay(); // 月维度起始时刻
        LocalDateTime yearStart = yearStartDate.atStartOfDay(); // 年维度起始时刻
        List<DeviceHealthHistory> histories = new ArrayList<>(); // 收集待落库的历史记录
        for (Map<String, Object> device : devices) { // 遍历每台主机汇总当日/月/年历史
            String hostId = MapUtils.getString(device, "device_id"); // 主机ID用于查询健康得分快照
            if (StringUtils.isBlank(hostId)) {
                // 无主机标识的记录跳过，避免无效查询
                continue;
            }
            DeviceHealthHistory dayHistory = buildHistory(hostId, "day", today, dayStart, dayEnd); // 日维度：统计当天全部得分
            if (dayHistory != null) {
                histories.add(dayHistory); // 收集日历史记录
            }
            DeviceHealthHistory monthHistory = buildHistory(hostId, "month", monthStartDate, monthStart, dayEnd); // 月维度：月初至今的统计
            if (monthHistory != null) {
                histories.add(monthHistory); // 收集月历史记录
            }
            DeviceHealthHistory yearHistory = buildHistory(hostId, "year", yearStartDate, yearStart, dayEnd); // 年维度：年初至今的统计
            if (yearHistory != null) {
                histories.add(yearHistory); // 收集年历史记录
            }
        }
        if (CollectionUtils.isNotEmpty(histories)) {
            healthHistoryMapper.upsertBatch(histories); // 批量写入日/月/年历史评分，避免多次数据库调用
        }
    }

    private Map<String, MetricValue> collectMetricValues(DeviceTemperatureWindow temperatureWindow,
                                                         DeviceAlarmWindow alarmWindow,
                                                         DeviceFaultWindow faultWindow,
                                                         DeviceElectricalWindow electricalWindow) {
        Map<String, MetricValue> map = new HashMap<>(); // 初始化指标容器，后续按编码存放指标值
        if (temperatureWindow != null) { // 温度窗口存在时获取各测点最高温度
            BigDecimal maxTemperature = max(
                temperatureWindow.getMaxAstatortemp1(), // A相定子温度峰值
                temperatureWindow.getMaxBstatortemp1(), // B相定子温度峰值
                temperatureWindow.getMaxCstatortemp1(), // C相定子温度峰值
                temperatureWindow.getMaxShaftendtemp(), // 轴端最高温度
                temperatureWindow.getMaxNonshaftendtemp(), // 非轴端最高温度
                temperatureWindow.getMaxWaterpumpbearingtemp() // 水泵轴承最高温度
            ); // 从多个测点中取最大值参与评分
            map.put("temperature.max", new MetricValue(maxTemperature, temperatureWindow.getSampleCount())); // 保存温度峰值及采样数，用于linear_desc公式
        }
        if (alarmWindow != null) { // 报警窗口存在时统计报警次数
            map.put("alarm.total", new MetricValue(toDecimal(alarmWindow.getTotalAlarmCount()), null)); // 报警总数走alarm_freq公式：100-报警数/预警阈值*100
        }
        if (faultWindow != null) { // 故障窗口存在时记录累计故障
            map.put("fault.total", new MetricValue(toDecimal(faultWindow.getTotalFaultCount()), null)); // 故障总数按linear_desc公式折算得分
            map.put("fault.events", new MetricValue(toDecimal(faultWindow.getFaultEventCount()), null)); // 故障事件数用于明细展示
        }
        if (electricalWindow != null) { // 电参窗口存在时记录功率因数
            map.put("electrical.powerfactor", new MetricValue(electricalWindow.getAvgPowerfactor(), electricalWindow.getSampleCount())); // 功率因数按linear_asc公式评分，值越接近1越高
        }
        return map; // 返回指标集合供评分与明细构建使用
    }

    private DeviceHealthScore buildScore(String deviceId,
                                         Map<String, MetricValue> metrics,
                                         Map<String, ThresholdRule> rules,
                                         DeviceTemperatureWindow temperatureWindow,
                                         DeviceAlarmWindow alarmWindow,
                                         DeviceFaultWindow faultWindow,
                                         DeviceElectricalWindow electricalWindow,
                                         LocalDateTime now) {
        BigDecimal temperatureScore = calculateScore(metrics.get("temperature.max"), rules.get("temperature.max")); // 温度指标按linear_desc公式计算：100-(实测-预警)/(报警-预警)*100
        BigDecimal alarmScore = calculateScore(metrics.get("alarm.total"), rules.get("alarm.total")); // 报警指标按alarm_freq公式计算：100-报警次数/预警阈值*100
        BigDecimal faultScore = calculateScore(metrics.get("fault.total"), rules.get("fault.total")); // 故障指标按linear_desc公式计算，故障越多扣分越多
        BigDecimal electricalScore = calculateScore(metrics.get("electrical.powerfactor"), rules.get("electrical.powerfactor")); // 功率因数按linear_asc公式计算：(实测-报警)/(预警-报警)*100

        List<WeightedScore> weightedScores = new ArrayList<>(); // 存放有效单项得分及其权重
        if (temperatureScore != null) { // 单项得分非空才参与综合
            weightedScores.add(new WeightedScore(temperatureScore, resolveWeight(rules.get("temperature.max")))); // 温度权重取score_weight，缺省为0.25
        }
        if (alarmScore != null) { // 报警得分有效时加入权重计算
            weightedScores.add(new WeightedScore(alarmScore, resolveWeight(rules.get("alarm.total")))); // 报警权重来源阈值配置
        }
        if (faultScore != null) { // 故障得分有效时加入权重计算
            weightedScores.add(new WeightedScore(faultScore, resolveWeight(rules.get("fault.total")))); // 故障权重沿用阈值配置
        }
        if (electricalScore != null) { // 功率因数得分有效时加入权重计算
            weightedScores.add(new WeightedScore(electricalScore, resolveWeight(rules.get("electrical.powerfactor")))); // 电参权重默认0.25，可被配置覆盖
        }
        if (weightedScores.isEmpty()) { // 如果四项得分都缺失则无法汇总
            return null; // 返回null表示本次无需写库
        }
        BigDecimal totalScore = aggregateTotal(weightedScores); // 对所有有效得分进行加权平均，确保权重归一化
        LocalDateTime scoreTime = resolveScoreTime(temperatureWindow, alarmWindow, faultWindow, electricalWindow); // 使用最新窗口结束时间作为得分时间戳
        DeviceHealthScore score = new DeviceHealthScore(); // 创建健康得分实体对象
        score.setDeviceId(deviceId); // 赋值设备ID
        score.setScoreTime(scoreTime != null ? scoreTime : now); // 取窗口结束时间，若为空则使用当前时间
        score.setScoreTotal(scale(totalScore)); // 综合得分保留两位小数
        score.setScoreTemperature(scale(temperatureScore)); // 温度得分保留两位小数
        score.setScoreAlarm(scale(alarmScore)); // 报警得分保留两位小数
        score.setScoreFault(scale(faultScore)); // 故障得分保留两位小数
        score.setScoreElectrical(scale(electricalScore)); // 电参得分保留两位小数
        score.setAlarmLevelMix(toJson(buildAlarmMix(alarmWindow, faultWindow))); // 构建报警&故障明细JSON便于前端展示
        score.setEvaluateDetail(toJson(buildDetail(metrics, rules))); // 构建指标原值、阈值、权重明细用于追溯
        score.setCreatedAt(now); // 设置创建时间戳
        score.setUpdatedAt(now); // 设置更新时间戳
        return score; // 返回完整的健康得分对象
    }

    private Map<String, Object> buildAlarmMix(DeviceAlarmWindow alarmWindow, DeviceFaultWindow faultWindow) {
        Map<String, Object> map = new LinkedHashMap<>();
        if (alarmWindow != null) {
            map.put("quickstrike", safeInt(alarmWindow.getQuickstrikeSum()));
            map.put("overcurrent", safeInt(alarmWindow.getOvercurrentSum()));
            map.put("thermal", safeInt(alarmWindow.getThermalSum()));
            map.put("overpressure", safeInt(alarmWindow.getOverpressureSum()));
            map.put("heatWarn", safeInt(alarmWindow.getHeatWarnSum()));
            map.put("zeroAlarm", safeInt(alarmWindow.getZeroAlarmSum()));
            map.put("loadWarn", safeInt(alarmWindow.getLoadWarnSum()));
            map.put("totalAlarm", safeInt(alarmWindow.getTotalAlarmCount()));
        }
        if (faultWindow != null) {
            map.put("faultTotal", safeInt(faultWindow.getTotalFaultCount()));
            map.put("faultEvents", safeInt(faultWindow.getFaultEventCount()));
        }
        return map;
    }

    private Map<String, Object> buildDetail(Map<String, MetricValue> metrics,
                                             Map<String, ThresholdRule> rules) {
        Map<String, Object> detail = new LinkedHashMap<>();
        for (Map.Entry<String, MetricValue> entry : metrics.entrySet()) {
            Map<String, Object> metricDetail = new LinkedHashMap<>();
            MetricValue metricValue = entry.getValue();
            if (metricValue != null && metricValue.getValue() != null) {
                metricDetail.put("value", metricValue.getValue());
            }
            if (metricValue != null && metricValue.getSamples() != null) {
                metricDetail.put("samples", metricValue.getSamples());
            }
            ThresholdRule rule = rules.get(entry.getKey());
            if (rule != null) {
                metricDetail.put("warn", rule.getWarnValue());
                metricDetail.put("alarm", rule.getAlarmValue());
                metricDetail.put("formula", rule.getScoringFormula());
                metricDetail.put("weight", rule.getWeight());
            }
            detail.put(entry.getKey(), metricDetail);
        }
        return detail;
    }

    private String toJson(Object object) {
        if (object == null) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            log.warn("序列化 JSON 时发生异常: {}", object, e);
            return null;
        }
    }

    private BigDecimal aggregateTotal(List<WeightedScore> scores) {
        BigDecimal weightSum = ZERO;
        BigDecimal weightedSum = ZERO;
        for (WeightedScore score : scores) {
            BigDecimal weight = score.getWeight();
            if (weight == null || weight.compareTo(ZERO) <= 0) {
                continue;
            }
            if (score.getScore() == null) {
                continue;
            }
            weightedSum = weightedSum.add(score.getScore().multiply(weight));
            weightSum = weightSum.add(weight);
        }
        if (weightSum.compareTo(ZERO) <= 0) {
            return null;
        }
        return weightedSum.divide(weightSum, 4, RoundingMode.HALF_UP);
    }

    private BigDecimal calculateScore(MetricValue metricValue, ThresholdRule rule) {
        if (metricValue == null || metricValue.getValue() == null) {
            return null;
        }
        if (rule == null) {
            return HUNDRED;
        }
        BigDecimal value = metricValue.getValue();
        BigDecimal warn = rule.getWarnValue();
        BigDecimal alarm = rule.getAlarmValue();
        String formula = rule.getScoringFormula();
        if (StringUtils.equalsIgnoreCase(formula, "linear_asc")) {
            return calculateLinearAsc(value, warn, alarm);
        }
        if (StringUtils.equalsIgnoreCase(formula, "alarm_freq")) {
            return calculateAlarmFreq(value, warn);
        }
        if (StringUtils.equalsIgnoreCase(formula, "linear_desc")) {
            return calculateLinearDesc(value, warn, alarm);
        }
        // 未配置或未知的公式统一按 linear_desc 处理，避免直接丢分
        return calculateLinearDesc(value, warn, alarm);
    }

    private BigDecimal calculateLinearDesc(BigDecimal value, BigDecimal warn, BigDecimal alarm) {
        if (value == null || warn == null || alarm == null) {
            return null;
        }
        if (value.compareTo(warn) <= 0) {
            return HUNDRED;
        }
        if (value.compareTo(alarm) >= 0) {
            return ZERO;
        }
        BigDecimal numerator = value.subtract(warn);
        BigDecimal denominator = alarm.subtract(warn);
        if (denominator.compareTo(ZERO) == 0) {
            return ZERO;
        }
        BigDecimal ratio = numerator.divide(denominator, 6, RoundingMode.HALF_UP);
        BigDecimal score = HUNDRED.subtract(ratio.multiply(HUNDRED));
        return normalizeScore(score);
    }

    private BigDecimal calculateLinearAsc(BigDecimal value, BigDecimal warn, BigDecimal alarm) {
        if (value == null || warn == null || alarm == null) {
            return null;
        }
        if (value.compareTo(warn) >= 0) {
            return HUNDRED;
        }
        if (value.compareTo(alarm) <= 0) {
            return ZERO;
        }
        BigDecimal numerator = value.subtract(alarm);
        BigDecimal denominator = warn.subtract(alarm);
        if (denominator.compareTo(ZERO) == 0) {
            return ZERO;
        }
        BigDecimal ratio = numerator.divide(denominator, 6, RoundingMode.HALF_UP);
        BigDecimal score = ratio.multiply(HUNDRED);
        return normalizeScore(score);
    }

    /**
     * 告警频次得分计算：value 代表统计窗口内的告警次数，warn 代表允许的最大告警次数。
     * 1) 告警次数或阈值缺失时无法计算，返回空。
     * 2) 使用 value / warn 得到超标比例，并按比例从 100 分中扣减。
     * 3) 超过 100% 时直接降为 0，低于 0% 时上限为 100，最终返回 0~100 的分值用于后续加权。
     */
    private BigDecimal calculateAlarmFreq(BigDecimal value, BigDecimal warn) {
        if (value == null || warn == null || warn.compareTo(ZERO) <= 0) {
            return null;
        }
        // 告警次数相对阈值的占比，保留 6 位小数以避免精度丢失
        BigDecimal ratio = value.divide(warn, 6, RoundingMode.HALF_UP);
        // 按比例从满分 100 中扣减
        BigDecimal deduction = ratio.multiply(HUNDRED);
        BigDecimal score = HUNDRED.subtract(deduction);
        if (score.compareTo(ZERO) < 0) {
            return ZERO;
        }
        if (score.compareTo(HUNDRED) > 0) {
            return HUNDRED;
        }
        return score;
    }

    private BigDecimal normalizeScore(BigDecimal score) {
        if (score.compareTo(ZERO) < 0) {
            return ZERO;
        }
        if (score.compareTo(HUNDRED) > 0) {
            return HUNDRED;
        }
        return score;
    }

    private ThresholdRule mergeRule(ThresholdRule original, DeviceThresholdConfig config) {
        if (config == null) {
            return original;
        }
        BigDecimal weight = config.getScoreWeight();
        return new ThresholdRule(
            config.getMetricCode(),
            config.getAlarmLevel(),
            config.getWarnValue(),
            config.getAlarmValue(),
            weight != null ? weight : (original != null ? original.getWeight() : DEFAULT_WEIGHT),
            config.getScoringFormula()
        );
    }

    private Map<String, ThresholdRule> loadThresholdRules(String deviceId) {
        Map<String, ThresholdRule> result = new HashMap<>();
        List<DeviceThresholdConfig> globalRules = thresholdConfigMapper.selectByDeviceId(null);
        if (CollectionUtils.isNotEmpty(globalRules)) {
            for (DeviceThresholdConfig config : globalRules) {
                if (!"score".equalsIgnoreCase(config.getAlarmLevel())) {
                    continue;
                }
                result.put(config.getMetricCode(), mergeRule(null, config));
            }
        }
        List<DeviceThresholdConfig> deviceRules = thresholdConfigMapper.selectByDeviceId(deviceId);
        if (CollectionUtils.isNotEmpty(deviceRules)) {
            for (DeviceThresholdConfig config : deviceRules) {
                if (!"score".equalsIgnoreCase(config.getAlarmLevel())) {
                    continue;
                }
                result.put(config.getMetricCode(), mergeRule(result.get(config.getMetricCode()), config));
            }
        }
        return result;
    }

    private BigDecimal resolveWeight(ThresholdRule rule) {
        if (rule == null || rule.getWeight() == null || rule.getWeight().compareTo(ZERO) <= 0) {
            return DEFAULT_WEIGHT;
        }
        return rule.getWeight();
    }

    private DeviceHealthHistory buildHistory(String deviceId,
                                             String dimension,
                                             LocalDate statDate,
                                             LocalDateTime start,
                                             LocalDateTime end) {
        List<DeviceHealthScore> scores = healthScoreMapper.selectByRange(deviceId, start, end);
        if (CollectionUtils.isEmpty(scores)) {
            return null;
        }
        List<BigDecimal> values = scores.stream()
            .map(DeviceHealthScore::getScoreTotal)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(values)) {
            return null;
        }
        BigDecimal avg = values.stream()
            .reduce(BigDecimal.ZERO, BigDecimal::add)
            .divide(BigDecimal.valueOf(values.size()), 4, RoundingMode.HALF_UP);
        BigDecimal max = values.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
        BigDecimal min = values.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
        DeviceHealthHistory history = new DeviceHealthHistory();
        history.setDeviceId(deviceId);
        history.setDimension(dimension);
        history.setStatDate(statDate);
        history.setScoreAvg(scale(avg));
        history.setScoreMax(scale(max));
        history.setScoreMin(scale(min));
        history.setCreatedAt(LocalDateTime.now());
        return history;
    }

    private BigDecimal scale(BigDecimal value) {
        if (value == null) {
            return null;
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }

    private Integer safeInt(Integer value) {
        return value == null ? 0 : value;
    }

    private BigDecimal max(BigDecimal... values) {
        BigDecimal result = null;
        if (values != null) {
            for (BigDecimal value : values) {
                if (value == null) {
                    continue;
                }
                if (result == null || value.compareTo(result) > 0) {
                    result = value;
                }
            }
        }
        return result;
    }

    private BigDecimal toDecimal(Integer value) {
        return value == null ? null : BigDecimal.valueOf(value);
    }

    private LocalDateTime resolveScoreTime(DeviceTemperatureWindow temperatureWindow,
                                           DeviceAlarmWindow alarmWindow,
                                           DeviceFaultWindow faultWindow,
                                           DeviceElectricalWindow electricalWindow) {
        LocalDateTime latest = null;
        latest = latest(latest, temperatureWindow != null ? temperatureWindow.getWindowEnd() : null);
        latest = latest(latest, alarmWindow != null ? alarmWindow.getWindowEnd() : null);
        latest = latest(latest, faultWindow != null ? faultWindow.getWindowEnd() : null);
        latest = latest(latest, electricalWindow != null ? electricalWindow.getWindowEnd() : null);
        return latest;
    }

    private LocalDateTime latest(LocalDateTime current, LocalDateTime candidate) {
        if (candidate == null) {
            return current;
        }
        if (current == null || candidate.isAfter(current)) {
            return candidate;
        }
        return current;
    }

    private static class ThresholdRule {

        private final String metricCode;
        private final String alarmLevel;
        private final BigDecimal warnValue;
        private final BigDecimal alarmValue;
        private final BigDecimal weight;
        private final String scoringFormula;

        ThresholdRule(String metricCode,
                      String alarmLevel,
                      BigDecimal warnValue,
                      BigDecimal alarmValue,
                      BigDecimal weight,
                      String scoringFormula) {
            this.metricCode = metricCode;
            this.alarmLevel = alarmLevel;
            this.warnValue = warnValue;
            this.alarmValue = alarmValue;
            this.weight = weight;
            this.scoringFormula = scoringFormula;
        }

        public String getMetricCode() {
            return metricCode;
        }

        public String getAlarmLevel() {
            return alarmLevel;
        }

        public BigDecimal getWarnValue() {
            return warnValue;
        }

        public BigDecimal getAlarmValue() {
            return alarmValue;
        }

        public BigDecimal getWeight() {
            return weight;
        }

        public String getScoringFormula() {
            return scoringFormula;
        }
    }

    private static class MetricValue {

        private final BigDecimal value;
        private final Integer samples;

        MetricValue(BigDecimal value, Integer samples) {
            this.value = value;
            this.samples = samples;
        }

        public BigDecimal getValue() {
            return value;
        }

        public Integer getSamples() {
            return samples;
        }
    }

    private static class WeightedScore {

        private final BigDecimal score;
        private final BigDecimal weight;

        WeightedScore(BigDecimal score, BigDecimal weight) {
            this.score = score;
            this.weight = weight;
        }

        public BigDecimal getScore() {
            return score;
        }

        public BigDecimal getWeight() {
            return weight;
        }
    }
}
